package com.aboverock.module.shiro.realms;

import com.aboverock.common.enums.UserStatusEnum;
import com.aboverock.core.shiro.util.ShiroHashEncryptUtil;
import com.aboverock.core.token.LocalUserLoginToken;
import com.aboverock.module.system.domain.Resource;
import com.aboverock.module.system.domain.Role;
import com.aboverock.module.system.domain.User;
import com.aboverock.module.system.service.ResourceService;
import com.aboverock.module.system.service.UserService;
import com.aboverock.module.system.utils.UserInfoUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

/**
 * 自定义数据库权限匹配和账号密码匹配.
 * 
 * @author Rock Wang
 *
 */
public class DbShiroRealm extends AuthorizingRealm {
    private Logger logger = LoggerFactory.getLogger(getClass());

    private boolean localUserSuppoert;

    @Autowired
    @Lazy
    private UserService userService;

    @Autowired
    @Lazy
    private ResourceService resourceService;

    public DbShiroRealm(String authorizationCacheName, boolean localUserSuppoert) {
        setAuthorizationCacheName(authorizationCacheName);
        this.localUserSuppoert = localUserSuppoert;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof LocalUserLoginToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
        String loginName = (String) SecurityUtils.getSubject().getPrincipal();

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        List<Resource> resources = new ArrayList<Resource>();
        if (UserInfoUtil.isSuperAdminName(loginName)) {
            resources = resourceService.list(null);
        } else {
            resources = resourceService.getResourcesByLoginName(loginName);
        }
        Set<String> permissions = resources.stream()
                .filter(resource -> !StringUtils.isBlank(resource.getPermissions()))
                .map(Resource::getPermissions).collect(Collectors.toSet());
        authorizationInfo.setStringPermissions(permissions);
        if (logger.isDebugEnabled()) {
            permissions.forEach(permission -> logger.debug("Data-Smart: 添加当前用户权限：{}", permission));
        }

        User user = userService.getCurrentUserWithRoles();
        if (user != null) {
            Set<String> roleCodes = user.getRoles().stream().map(Role::getCode)
                    .collect(Collectors.toSet());
            authorizationInfo.setRoles(roleCodes);
            if (logger.isDebugEnabled()) {
                roleCodes.forEach(code -> logger.debug("Data-Smart: 添加当前用户角色权限：{}", code));
            }
        }

        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取用户登陆名
        String loginName = (String) token.getPrincipal();

        SimpleAuthenticationInfo authenticationInfo = null;
        // 如果是超级管理员或支持本地数据库用户登陆
        if (UserInfoUtil.isSuperAdminName(loginName) || localUserSuppoert) {
            logger.debug("Data-Smart: 数据库认证登陆用户{}", loginName);
            // 通过loginName从数据库中查找 User对象.
            User user = userService.getLocalUserByLoginName(loginName);
            if (user == null) {
                logger.debug("Data-Smart: 数据库中用户{}不存在", loginName);
                throw new UnknownAccountException(loginName + "'s account not exist");
            }
            // 帐户锁定，超级管理员不能被锁定
            if (user.getStatus().equals(UserStatusEnum.LOCKED.getValue())) { 
                throw new LockedAccountException(loginName + "'s account is locked");
            }
            authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), 
                    ByteSource.Util.bytes(ShiroHashEncryptUtil.HASH_SALT),
                    // realm name
                    getName()
            );
        }
        return authenticationInfo;
    }

    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher hashCredentialsMatcher = new HashedCredentialsMatcher();
        hashCredentialsMatcher.setHashAlgorithmName(ShiroHashEncryptUtil.HASH_ALGORITHM);
        hashCredentialsMatcher.setHashIterations(ShiroHashEncryptUtil.HASH_ITERATIONS);
        super.setCredentialsMatcher(hashCredentialsMatcher);
    }

    public boolean isLocalUserSuppoert() {
        return localUserSuppoert;
    }

}
